package com.example.jbmsuppliers.bluetooth.le;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.jbm.jbmsupplier.util.IEGuardKey;

import java.util.List;
import java.util.UUID;

import static android.bluetooth.BluetoothAdapter.STATE_CONNECTED;
import static android.bluetooth.BluetoothAdapter.STATE_DISCONNECTED;
import static com.example.jbmsuppliers.bluetooth.le.BluetoothLeService.ACTION_GATT_DISCONNECTED;
import static com.example.jbmsuppliers.bluetooth.le.BluetoothLeService.UUID_HEART_RATE_MEASUREMENT;

/**
 * Created by Sun on 2016/11/10.
 */

public class BTKeyGattCallback extends BluetoothGattCallback implements Life
{
    private final static String TAG = BTKeyGattCallback.class.getSimpleName();
    private int mConnectionState = STATE_DISCONNECTED;
    private IEGuardKey ekey;
    private BluetoothAdapter mBluetoothAdapter;
    protected BluetoothGatt mBluetoothGatt;
    private DoorEvent doorevent = null;
    private BluetoothGattCharacteristic notifycha = null;
    private BluetoothGattCharacteristic writecha = null;
    private String sname;
    private boolean firstkey = false;

    public BTKeyGattCallback()
    {
        resource = null;
    }

    public BTKeyGattCallback(IEGuardKey ekey, DoorEvent doorevent, String sname )
    {
        this.ekey = ekey;
        this.doorevent = doorevent;
        this.sname = sname;
    }

    public void Init (IEGuardKey ekey, DoorEvent doorevent, String sname )
    {
        this.ekey = ekey;
        this.doorevent = doorevent;
        this.sname = sname;
    }

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        String intentAction;
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            mConnectionState = STATE_CONNECTED;
            Log.i(TAG, "Connected to GATT server.");
            if ( doorevent != null)
                doorevent.OpenStatus("连接成功，搜索服务...");
                // Attempts to discover services after successful connection.
            Log.i(TAG, "Attempting to start service discovery:" +
                    mBluetoothGatt.discoverServices());
            if ( this.resource != null )
                SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s begin disservice %s rs=%d", sname, ekey.mac, resource.resource.size());
            else
                SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s begin disservice %s", sname, ekey.mac );
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            intentAction = ACTION_GATT_DISCONNECTED;
            mConnectionState = STATE_DISCONNECTED;
            Log.i(TAG, "Disconnected from GATT server.");
            if ( this.resource != null )
                SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s on disconnect %s rs=%d", sname, ekey.mac, resource.resource.size());
            else
                SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s on disconnect %s", sname, ekey.mac );
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status)
    {
        if ( this.resource != null )
            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s do disservice %s status=%d", sname, ekey.mac, status);
        if (status == BluetoothGatt.GATT_SUCCESS)
        {
            if ( doorevent != null)
                doorevent.OpenStatus("搜索到服务!");
            List<BluetoothGattService> BTDev = getSupportedGattServices();
            if (BTDev != null)
                Log.d(TAG, "Get " + BTDev.size() + " service");
            else
                Log.d(TAG, "Get ...... service");
            if (BTDev != null)
                for (BluetoothGattService gattsrv : BTDev)
                {
                    List<BluetoothGattCharacteristic> gattchas = gattsrv.getCharacteristics();

                    for (BluetoothGattCharacteristic gattcha : gattchas)
                    {
                        int prop = gattcha.getProperties();
                        if ((prop & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0)
                            notifycha = gattcha;
                        if ((prop & ( BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE |
                                BluetoothGattCharacteristic.PROPERTY_WRITE)) > 0)
                            writecha = gattcha;
                    }
                }
            if ( notifycha != null )
            {
                setCharacteristicNotification(notifycha, false);
                setCharacteristicNotification(notifycha, true);
            }

            if ( writecha != null && !firstkey )
            {
                writecha.setValue(0,
                        BluetoothGattCharacteristic.FORMAT_UINT8, 0);
                writecha.setValue("@04000000001603*");
                if ( this.resource != null )
                    SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s send first key %s rs=%d", sname, ekey.mac, resource.resource.size());
                else
                    SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s send first key %s", sname, ekey.mac );
                writeCharacteristic(writecha);

                firstkey = true;
            }
        }else {
            Log.w(TAG, "onServicesDiscovered received: " + status);
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic characteristic,
                                     int status)
    {
    }

    private String checkCRC(String inStr)
    {
        try{
            byte[] buf =inStr.getBytes("GBK");
            byte bCheck = 0;
            for (int i = 0; i < buf.length; i++)
            {
                bCheck = (byte)((int)bCheck ^ (int)buf[i]);
            }
            return String.format("%02X", bCheck);
        }
        catch(Exception e){return "00";}
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
                                        BluetoothGattCharacteristic characteristic) {
        if ( characteristic.getUuid().toString().equals(
                notifycha.getUuid().toString() ) )
        {
            final byte[] data = characteristic.getValue();
            if (data != null && data.length > 0)
            {
                String reqCMD = new String(data);
                if (reqCMD.indexOf("*") > 0)
                {
                    if (reqCMD.indexOf("@03") >= 0 )
                    {
                        Log.i(TAG, "回复退出."+reqCMD);
                        if ( this.resource != null )
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s opendoor suc %s rs=%d", sname, ekey.mac, resource.resource.size());
                        else
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s opendoor suc %s", sname, ekey.mac );
                        disconnect();
                        close();
                        ReleaseFromPool();
                        Die();
                        if ( doorevent != null)
                            doorevent.OpenSuccess(ekey, ekey.mac);
                    } else if ( reqCMD.indexOf("@ER") >= 0 )
                    {
                        Log.i(TAG, "回复错误."+reqCMD);
                        if ( this.resource != null )
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s wrong @ER %s rs=%d", sname, ekey.mac, resource.resource.size());
                        else
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s wrong @ER %s", sname, ekey.mac );
                        if ( doorevent != null)
                            doorevent.OpenStatus("指令处理错误");
                    }else if (reqCMD.indexOf("@04") >= 0)
                    {
                        if ( this.resource != null )
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s dy key back %s rs=%d", sname, ekey.mac, resource.resource.size());
                        else
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s dy key back %s", sname, ekey.mac );
                        String TID = ekey.open_key.substring(3, 11);
                        String KEY = reqCMD.substring(5, 9);
                        int sKey = Integer.parseInt(KEY, 16);
                        int sNum = sKey % 16;
                        sKey = ((sKey << sNum) | (sKey >> (16 - sNum)));
                        byte[] KEYS = new byte[2];
                        KEYS[0] = (byte) (sKey / 256);
                        KEYS[1] = (byte) (sKey % 256);
                        byte[] TIDS = new byte[4];
                        TIDS[0] = (byte) Integer.parseInt(TID.substring(0, 2), 16);
                        TIDS[1] = (byte) Integer.parseInt(TID.substring(2, 4), 16);
                        TIDS[2] = (byte) Integer.parseInt(TID.substring(4, 6), 16);
                        TIDS[3] = (byte) Integer.parseInt(TID.substring(6, 8), 16);
                        for (int i = 0; i < 4; i++)
                        {
                            TIDS[i] = (byte) (TIDS[i] ^ KEYS[i % 2]);
                        }
                        String mCmd = ekey.open_key.replace(TID, String.format("%02X", TIDS[0]) + String.format("%02X", TIDS[1]) + String.format("%02X", TIDS[2]) + String.format("%02X", TIDS[3]));
                        mCmd = mCmd.substring(0, mCmd.length() - 3) + checkCRC(mCmd.substring(1, mCmd.length() - 3)) + "*";

                        characteristic.setValue(0,
                                BluetoothGattCharacteristic.FORMAT_UINT8, 0);
                        characteristic.setValue(mCmd);
                        if ( doorevent != null)
                            doorevent.OpenStatus("发出开门指令");
                        writeCharacteristic(characteristic);
                        if ( this.resource != null )
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s send second key %s rs=%d", sname, ekey.mac, resource.resource.size());
                        else
                            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s send second key %s", sname, ekey.mac );
                    }
                }
            }
        }
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic characteristic,
                                     int status)
    {
        Log.w(TAG, "onServicesDiscovered received: ");
    }

    /**
     * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
     * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
     * callback.
     *
     * @param characteristic The characteristic to read from.
     */
    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if ( mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.readCharacteristic(characteristic);
    }
    public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
        if ( mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.writeCharacteristic(characteristic);
    }

    /**
     * Enables or disables notification on a give characteristic.
     *
     * @param characteristic Characteristic to act on.
     * @param enabled If true, enable notification.  False otherwise.
     */
    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                              boolean enabled) {
        if ( mBluetoothGatt == null) {
            Log.w(TAG, "BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

        // This is specific to Heart Rate Measurement.
        if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                    UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            mBluetoothGatt.writeDescriptor(descriptor);
        }
    }

    public List<BluetoothGattService> getSupportedGattServices() {
        if (mBluetoothGatt == null) return null;

        return mBluetoothGatt.getServices();
    }

    public boolean Open(BluetoothAdapter BluetoothAdapter, final int retry, final int timeout, final Resource resource, final boolean reconnect )
    {
        if ( BluetoothAdapter != null )
            mBluetoothAdapter = BluetoothAdapter;

        if ( mBluetoothGatt != null )
            return false;

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(ekey.mac);
        if (device == null)
        {
            Log.w(TAG, "Device not found.  Unable to connect.");
            if ( doorevent != null )
                doorevent.OtherError("未连接成功指定设备："+ekey.mac);
            return false;
        }else
        {
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable()
            {
                @Override
                public void run()
                {
                if (resource != null)
                    SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG, "%s connect to %s rs=%d", sname, ekey.mac, resource.resource.size());
                else
                    SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG, "%s connect to %s", sname, ekey.mac);


                mBluetoothGatt = device.connectGatt(resource, false, BTKeyGattCallback.this);


                if (mBluetoothGatt != null)
                {
                    if ( !reconnect )
                    {
                        Born(retry, timeout);
                        resource.Add(BTKeyGattCallback.this);
                    }

                    if (resource != null)
                        SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG, "%s connected %s rs=%d ln=%s", sname, ekey.mac, resource.resource.size(), lifename.toString());
                    else
                        SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG, "%s connected %s ln=%s", sname, ekey.mac, lifename.toString());
                }else
                    if (resource != null)
                        SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG, "%s connect failed %s rs=%d", sname, ekey.mac, resource.resource.size());
                    else
                        SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG, "%s connect failed %s", sname, ekey.mac);
                }
            });
        }

        return true;
    }

    public void disconnect()
    {
        firstkey = false;

        if ( this.resource != null )
            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s do disconnect %s rs=%d isnull=%b", sname, ekey.mac, resource.resource.size(), mBluetoothGatt == null );
        else
            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s do disconnect %s isnull=%b", sname, ekey.mac, mBluetoothGatt == null );
        if ( mBluetoothGatt != null )
            mBluetoothGatt.disconnect();
        return;
    }

    public void close()
    {
        if ( this.resource != null )
            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s do close %s rs=%d isnull=%b", sname, ekey.mac, resource.resource.size(), mBluetoothGatt == null );
        else
            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s do close %s isnull=%b", sname, ekey.mac, mBluetoothGatt == null );
        if ( mBluetoothGatt != null )
        {
            mBluetoothGatt.close();
            mBluetoothGatt = null;
        }
        return;
    }


    private int retry;
    private int timeout;
    private Object[] param;
    private Resource resource;
    private ResKey lifename;
    private int status;
    private long borntimems;
    private long lasttryms;
    private int trytimes;

    private int step;
    private int ctimeout;

    @Override
    public void Born(int retry, int timeout)
    {
        this.borntimems = System.currentTimeMillis();
        this.lasttryms = this.borntimems;
        this.retry = retry;
        this.timeout = timeout;
        this.trytimes = 0;

        step = 0;
        ctimeout = timeout+3000;
    }

    @Override
    public boolean AmILiving()
    {
        return true;
    }

    @Override
    public boolean IamLiving()
    {
        return true;
    }

    @Override
    public boolean RuLiving()
    {
        return true;
    }

    @Override
    public boolean RuLivingWell()
    {
        return true;
    }

    @Override
    public boolean Wakeup(Object... args)
    {
        long cms = System.currentTimeMillis();
        boolean send = false;

        if (lasttryms + ctimeout < cms)
        {
            lasttryms = cms;
            if ( step == 1 )
                trytimes++;
            send = true;
        }

        if ( trytimes <= retry )
        {
            if (send)
            {
                if ( step == 0 )
                {
                    step = 1;
                    if ( trytimes < retry )
                    {
                        ctimeout = 666;
                        disconnect();
                        close();
                    }else
                        ctimeout = 0;
                }else
                {
                    step = 0;
                    ctimeout = timeout;
                    Open(null, this.retry, this.timeout, this.resource, true );
                }
            }
        } else
        {
            disconnect();
            close();
            ReleaseFromPool();
            Die();
            if (doorevent != null)
                doorevent.OpenFailed(ekey, ekey.mac);
        }

        return true;
    }

    @Override
    public boolean IsLast(Object... args)
    {
        return true;
    }

    @Override
    public boolean Die()
    {
        return true;
    }

    @Override
    public boolean BindParameter(Object... param)
    {
        this.param = param;
        return true;
    }

    @Override
    public Object GetParameter(int index)
    {
        return this.param[index];
    }

    @Override
    public boolean BindLifeName(ResKey key)
    {
        this.lifename = key;
            return true;
    }

    @Override
    public ResKey GetLifeName()
    {
        return this.lifename;
    }

    @Override
    public boolean SetStatus(int status)
    {
        this.status = status;
        return true;
    }

    @Override
    public int GetStatus()
    {
        return status;
    }

    @Override
    public int GetOverTime()
    {
        return timeout;
    }

    @Override
    public boolean ReleaseFromPool()
    {
        if ( this.resource != null )
            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s do releasefrompool %s rs=%d", sname, ekey.mac, resource.resource.size());
        else
            SysLog.g_syslog.MakeLog(SysLog.SYS_LOG_LEVEL_DEBUG,"%s do releasefrompool %s", sname, ekey.mac);
        if ( resource != null )
            return resource.Drop(lifename);
        else
            return false;
    }

    @Override
    public boolean BindPool(Resource resource)
    {
        this.resource = resource;
        return true;
    }


    @Override
    public void SetUniqueIndex(Object uniqueidx)
    {
    }

    @Override
    public Object GetUniqueIndex()
    {
        return ekey.mac;
    }
}
